<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>SUDOKU</title>
    <style>
        H1 {
            background: black;
            color: white;
            margin: 0px 0px 2px 0px;
            font-size: 20px;
            padding: 2px 1px 5px 1px;
            text-align: center;
        }

        body {
            font-family: verdana, helvetica, arial, sans-serif;
            border: 0px;
            margin: 0px;
            padding: 0px;
            background: url() repeat;
        }

        .sudoku_board {
            margin: 6px auto;
            overflow: hidden;
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
            box-shadow: 0px 0px 5px 5px #bdc3c7;
        }

        .sudoku_board .cell {
            width: 11.11%;
            display: inline-block;
            float: left;
            cursor: pointer;
            text-align: center;
            overflow: hidden;
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box;
            box-shadow: 0px 0px 0px 1px #bdc3c7;
            background: white;
        }

        .sudoku_board .cell.border_h {
            box-shadow: 0px 0px 0px 1px #bdc3c7, inset 0px -2px 0 0 #34495e;
        }

        .sudoku_board .cell.border_v {
            box-shadow: 0px 0px 0px 1px #bdc3c7, inset -2px 0 0 #34495e;
        }

        .sudoku_board .cell.border_h.border_v {
            box-shadow: 0px 0px 0px 1px #bdc3c7, inset -2px 0 0 black, inset 0px -2px 0 black;
        }

        .sudoku_board .cell span {
            color: #2c3e50;
            font-size: 14px;
            text-align: middle;
        }

        .sudoku_board .cell.selected,
        .sudoku_board .cell.selected.fix {
            background: #ffe;
        }

        .sudoku_board .cell.selected.current {
            position: relative;
            background: #3498db;
            font-weight: bold;
            box-shadow: 0px 0px 3px 3px #bdc3c7;
        }

        .sudoku_board .cell.selected.current span {
            color: white;
        }

        .sudoku_board .cell.selected.group {
            color: blue;
        }

        .sudoku_board .cell span.samevalue,
        .sudoku_board .cell.fix span.samevalue {
            font-weight: bold;
            color: #3498db;
        }

        .sudoku_board .cell.notvalid,
        .sudoku_board .cell.selected.notvalid {
            font-weight: bold;
            color: white;
            background: #e74c3c;
        }

        .sudoku_board .cell.fix {
            background: #ecf0f1;
            cursor: not-allowed;
        }

        .sudoku_board .cell.fix span {
            color: #7f8c8d;
        }

        .sudoku_board .cell .solution {
            font-size: 10px;
            color: #d35400;
        }

        .sudoku_board .cell .note {
            color: #bdc3c7;
            width: 50%;
            height: 50%;
            display: inline-block;
            float: left;
            text-align: center;
            font-size: 14px;
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box;
        }

        .gameover_container .gameover {
            color: white;
            font-weight: bold;
            text-align: center;
            display: block;
            position: absolute;
            width: 90%;
            padding: 10px;
            box-shadow: 0px 0px 5px 5px #bdc3c7;
        }

        .restart {
            background: #7f8c8d;
            color: #ecf0f1;
        }

        .board_console_container,
        .gameover_container {
            background-color: rgba(127, 140, 141, 0.7);
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
        }

        .board_console {
            display: block;
            position: absolute;
            width: 50%;
            color: white;
            background-color: rgba(127, 140, 141, 0.7);
            box-shadow: 0px 0px 5px 5px #bdc3c7;
        }

        .board_console .num {
            width: 33.33%;
            color: #2c3e50;
            padding: 1px;
            display: inline-block;
            font-weight: bold;
            font-size: 24px;
            text-align: center;
            cursor: pointer;
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box;
            box-shadow: 0px 0px 0px 1px #bdc3c7;
        }

        .board_console .num:hover {
            color: white;
            background: #f1c40f;
        }

        .board_console .num.remove {
            width: 66.66%;
        }

        .board_console .num.note {
            background: #95a5a6;
            color: #ecf0f1;
        }

        .board_console .num.note:hover {
            background: #95a5a6;
            color: #f1c40f;
        }

        .board_console .num.selected {
            background: #f1c40f;
            box-shadow: 0px 0px 3px 3px #bdc3c7;
        }

        .board_console .num.note.selected {
            background: #f1c40f;
            box-shadow: 0px 0px 3px 3px #bdc3c7;
        }

        .board_console .num.note.selected:hover {
            color: white;
        }

        .board_console .num.no:hover {
            color: white;
            cursor: not-allowed;
        }

        .board_console .num.remove:hover {
            color: white;
            background: #c0392b;
        }

        .statistics {
            text-align: center;
        }

        #sudoku_menu {
            background-color: black;
            position: absolute;
            z-index: 2;
            width: 100%;
            height: 100%;
            left: -100%;
            box-sizing: border-box;
            -moz-box-sizing: border-box;
        }

        #sudoku_menu ul {
            margin: 0;
            padding: 100px 0px 0px 0px;
            list-style: none;
        }

        #sudoku_menu ul li {
            margin: 0px 50px;
        }

        #sudoku_menu ul li a {
            text-align: center;
            padding: 15px 20px;
            font-size: 28px;
            font-weight: bold;
            color: white;
            text-decoration: none;
            display: block;
            border-bottom: 1px solid #2c3e50;
        }

        #sudoku_menu.open-sidebar {
            left: 0px;
        }

        #sidebar-toggle {
            z-index: 3;
            background: #bdc3c7;
            border-radius: 3px;
            display: block;
            position: relative;
            padding: 22px 18px;
            float: left;
        }

        #sidebar-toggle .bar {
            display: block;
            width: 28px;
            margin-bottom: 4px;
            height: 4px;
            background-color: #f0f0f0;
            border-radius: 1px;
        }

        #sidebar-toggle .bar:last-child {
            margin-bottom: 0;
        }

        @media all and (orientation:portrait) and (min-width:640px) {
            h1 {
                font-size: 50px;
            }

            .statistics {
                font-size: 30px;
            }

            .sudoku_board .cell span {
                font-size: 60px;
            }

            .board_console .num {
                font-size: 60px;
            }
        }

        @media all and (orientation:landscape) and (min-height:640px) {
            h1 {
                font-size: 50px;
            }

            .statistics {
                font-size: 30px;
            }

            .sudoku_board .cell span {
                font-size: 50px;
            }

            .board_console .num {
                font-size: 50px;
            }
        }

        @media all and (orientation:portrait) and (max-width:1000px) {
            .sudoku_board .cell span {
                font-size: 30px;
            }
        }

        @media all and (orientation:portrait) and (max-width:640px) {
            .sudoku_board .cell span {
                font-size: 24px;
            }

            .sudoku_board .cell .note {
                font-size: 10px;
            }
        }

        @media all and (orientation:portrait) and (max-width:470px) {
            .sudoku_board .cell span {
                font-size: 16px;
            }

            .sudoku_board .cell .note {
                font-size: 8px;
            }
        }

        @media all and (orientation:portrait) and (max-width:320px) {
            .sudoku_board .cell span {
                font-size: 12px;
            }

            .sudoku_board .cell .note {
                font-size: 8px;
            }
        }

        @media all and (orientation:portrait) and (max-width:240px) {
            .sudoku_board .cell span {
                font-size: 10px;
            }
        }
    </style>
</head>

<body>
    <a id="sidebar-toggle"><span class="bar"></span><span class="bar"></span><span class="bar"></span></a>
    <h1 id="sudoku_title">数独</h1>
    <div id="sudoku_menu">
        <ul>
            <li><a class="restart">新游戏</a></li>
            <li></li>
        </ul>
    </div>
    <div id="sudoku_container"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
    <script>function Sudoku(params) { var t = this; this.INIT = 0; this.RUNNING = 1; this.END = 2; this.id = params.id || 'sudoku_container'; this.displaySolution = params.displaySolution || 0; this.displaySolutionOnly = params.displaySolutionOnly || 0; this.displayTitle = params.displayTitle || 0; this.highlight = params.highlight || 0; this.fixCellsNr = params.fixCellsNr || 32; this.n = 3; this.nn = this.n * this.n; this.cellsNr = this.nn * this.nn; if (this.fixCellsNr < 10) this.fixCellsNr = 10; if (this.fixCellsNr > 70) this.fixCellsNr = 70; this.init(); setInterval(function () { t.timer() }, 1000); return this } Sudoku.prototype.init = function () { this.status = this.INIT; this.cellsComplete = 0; this.board = []; this.boardSolution = []; this.cell = null; this.markNotes = 0; this.secondsElapsed = 0; if (this.displayTitle == 0) { $('#sudoku_title').hide() } this.board = this.boardGenerator(this.n, this.fixCellsNr); return this }; Sudoku.prototype.timer = function () { if (this.status === this.RUNNING) { this.secondsElapsed++; $('.time').text('' + this.secondsElapsed) } }; Sudoku.prototype.shuffle = function (array) { var currentIndex = array.length, temporaryValue = 0, randomIndex = 0; while (0 !== currentIndex) { randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1; temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue } return array }; Sudoku.prototype.boardGenerator = function (n, fixCellsNr) { var matrix_fields = [], index = 0, i = 0, j = 0, j_start = 0, j_stop = 0; this.boardSolution = []; for (i = 0; i < this.nn; i++) { matrix_fields[i] = i + 1 } matrix_fields = this.shuffle(matrix_fields); for (i = 0; i < n * n; i++) { for (j = 0; j < n * n; j++) { var value = Math.floor(((i * n + i / n + j) % (n * n)) + 1); this.boardSolution[index] = value; index++ } } var blank_indexes = []; for (i = 0; i < this.n; i++) { blank_indexes[i] = i + 1 } var bands_horizontal_indexes = this.shuffle(blank_indexes); var board_solution_tmp = []; index = 0; for (i = 0; i < bands_horizontal_indexes.length; i++) { j_start = (bands_horizontal_indexes[i] - 1) * this.n * this.nn; j_stop = bands_horizontal_indexes[i] * this.n * this.nn; for (j = j_start; j < j_stop; j++) { board_solution_tmp[index] = this.boardSolution[j]; index++ } } this.boardSolution = board_solution_tmp; var bands_vertical_indexes = this.shuffle(blank_indexes); board_solution_tmp = []; index = 0; for (k = 0; k < this.nn; k++) { for (i = 0; i < this.n; i++) { j_start = (bands_vertical_indexes[i] - 1) * this.n; j_stop = bands_vertical_indexes[i] * this.n; for (j = j_start; j < j_stop; j++) { board_solution_tmp[index] = this.boardSolution[j + k * this.nn]; index++ } } } this.boardSolution = board_solution_tmp; var board_indexes = [], board_init = []; for (i = 0; i < this.boardSolution.length; i++) { board_indexes[i] = i; board_init[i] = 0 } board_indexes = this.shuffle(board_indexes); board_indexes = board_indexes.slice(0, this.fixCellsNr); for (i = 0; i < board_indexes.length; i++) { board_init[board_indexes[i]] = this.boardSolution[board_indexes[i]]; if (parseInt(board_init[board_indexes[i]]) > 0) { this.cellsComplete++ } } return this.displaySolutionOnly ? this.boardSolution : board_init }; Sudoku.prototype.drawBoard = function () { var index = 0, position = { x: 0, y: 0 }, group_position = { x: 0, y: 0 }; var sudoku_board = $('<div></div>').addClass('sudoku_board'); var sudoku_statistics = $('<div></div>').addClass('statistics').html('<b>完成:</b> <span class="cells_complete">' + this.cellsComplete + '/' + this.cellsNr + '</span> <b>时间:</b> <span class="time">' + this.secondsElapsed + '</span>'); $('#' + this.id).empty(); for (i = 0; i < this.nn; i++) { for (j = 0; j < this.nn; j++) { position = { x: i + 1, y: j + 1 }; group_position = { x: Math.floor((position.x - 1) / this.n), y: Math.floor((position.y - 1) / this.n), }; var value = this.board[index] > 0 ? this.board[index] : '', value_solution = this.boardSolution[index] > 0 ? this.boardSolution[index] : '', cell = $('<div></div>').addClass('cell').attr('x', position.x).attr('y', position.y).attr('gr', group_position.x + '' + group_position.y).html('<span>' + value + '</span>'); if (this.displaySolution) { $('<span class="solution">(' + value_solution + ')</span>').appendTo(cell) } if (value > 0) { cell.addClass('fix') } if (position.x % this.n === 0 && position.x != this.nn) { cell.addClass('border_h') } if (position.y % this.n === 0 && position.y != this.nn) { cell.addClass('border_v') } cell.appendTo(sudoku_board); index++ } } sudoku_board.appendTo('#' + this.id); var sudoku_console_cotainer = $('<div></div>').addClass('board_console_container'); var sudoku_console = $('<div></div>').addClass('board_console'); for (i = 1; i <= this.nn; i++) { $('<div></div>').addClass('num').text(i).appendTo(sudoku_console) } $('<div></div>').addClass('num remove').text('X').appendTo(sudoku_console); $('<div></div>').addClass('num note').text('?').appendTo(sudoku_console); var sudoku_gameover = $('<div class="gameover_container"><div class="gameover">Congratulation! <button class="restart">Play Again</button></div></div>'); sudoku_console_cotainer.appendTo('#' + this.id).hide(); sudoku_console.appendTo(sudoku_console_cotainer); sudoku_statistics.appendTo('#' + this.id); sudoku_gameover.appendTo('#' + this.id).hide(); this.resizeWindow() }; Sudoku.prototype.resizeWindow = function () { console.time('resizeWindow'); var screen = { w: $(window).width(), h: $(window).height() }; var b_pos = $('#' + this.id + ' .sudoku_board').offset(), b_dim = { w: $('#' + this.id + ' .sudoku_board').width(), h: $('#' + this.id + ' .sudoku_board').height(), }, s_dim = { w: $('#' + this.id + ' .statistics').width(), h: $('#' + this.id + ' .statistics').height(), }; var screen_wr = screen.w + s_dim.h + b_pos.top + 10; if (screen_wr > screen.h) { $('#' + this.id + ' .sudoku_board').css('width', screen.h - b_pos.top - s_dim.h - 14); $('#' + this.id + ' .board_console').css('width', b_dim.h / 2) } else { $('#' + this.id + ' .sudoku_board').css('width', '98%'); $('#' + this.id + ' .board_console').css('width', '50%') } var cell_width = $('#' + this.id + ' .sudoku_board .cell:first').width(), note_with = Math.floor(cell_width / 2) - 1; $('#' + this.id + ' .sudoku_board .cell').height(cell_width); $('#' + this.id + ' .sudoku_board .cell span').css('line-height', cell_width + 'px'); $('#' + this.id + ' .sudoku_board .cell .note').css({ 'line-height': note_with + 'px', width: note_with, height: note_with, }); var console_cell_width = $('#' + this.id + ' .board_console .num:first').width(); $('#' + this.id + ' .board_console .num').css('height', console_cell_width); $('#' + this.id + ' .board_console .num').css('line-height', console_cell_width + 'px'); b_dim = { w: $('#' + this.id + ' .sudoku_board').width(), h: $('#' + this.id + ' .sudoku_board').width(), }; b_pos = $('#' + this.id + ' .sudoku_board').offset(); c_dim = { w: $('#' + this.id + ' .board_console').width(), h: $('#' + this.id + ' .board_console').height(), }; var c_pos_new = { left: b_dim.w / 2 - c_dim.w / 2 + b_pos.left, top: b_dim.h / 2 - c_dim.h / 2 + b_pos.top, }; $('#' + this.id + ' .board_console').css({ left: c_pos_new.left, top: c_pos_new.top, }); var gameover_pos_new = { left: screen.w / 20, top: screen.w / 20 + b_pos.top, }; $('#' + this.id + ' .gameover').css({ left: gameover_pos_new.left, top: gameover_pos_new.top, }); console.log('screen', screen); console.timeEnd('resizeWindow') }; Sudoku.prototype.showConsole = function (cell) { $('#' + this.id + ' .board_console_container').show(); var t = this, oldNotes = $(this.cell).find('.note'); $('#' + t.id + ' .board_console .num').removeClass('selected'); if (t.markNotes) { $('#' + t.id + ' .board_console .num.note').addClass('selected'); $.each(oldNotes, function () { var noteNum = $(this).text(); $('#' + t.id + ' .board_console .num:contains(' + noteNum + ')').addClass('selected') }) } return this }; Sudoku.prototype.hideConsole = function (cell) { $('#' + this.id + ' .board_console_container').hide(); return this }; Sudoku.prototype.cellSelect = function (cell) { this.cell = cell; var value = $(cell).text() | 0, position = { x: $(cell).attr('x'), y: $(cell).attr('y') }, group_position = { x: Math.floor((position.x - 1) / 3), y: Math.floor((position.y - 1) / 3), }, horizontal_cells = $('#' + this.id + ' .sudoku_board .cell[x="' + position.x + '"]'), vertical_cells = $('#' + this.id + ' .sudoku_board .cell[y="' + position.y + '"]'), group_cells = $('#' + this.id + ' .sudoku_board .cell[gr="' + group_position.x + '' + group_position.y + '"]'), same_value_cells = $('#' + this.id + ' .sudoku_board .cell span:contains(' + value + ')'); $('#' + this.id + ' .sudoku_board .cell').removeClass('selected current group'); $('#' + this.id + ' .sudoku_board .cell span').removeClass('samevalue'); $(cell).addClass('selected current'); if (this.highlight > 0) { horizontal_cells.addClass('selected'); vertical_cells.addClass('selected'); group_cells.addClass('selected group'); same_value_cells.not($(cell).find('span')).addClass('samevalue') } if ($(this.cell).hasClass('fix')) { $('#' + this.id + ' .board_console .num').addClass('no') } else { $('#' + this.id + ' .board_console .num').removeClass('no'); this.showConsole(); this.resizeWindow() } }; Sudoku.prototype.addValue = function (value) { console.log('prepare for addValue', value); var position = { x: $(this.cell).attr('x'), y: $(this.cell).attr('y') }, group_position = { x: Math.floor((position.x - 1) / 3), y: Math.floor((position.y - 1) / 3), }, horizontal_cells = '#' + this.id + ' .sudoku_board .cell[x="' + position.x + '"]', vertical_cells = '#' + this.id + ' .sudoku_board .cell[y="' + position.y + '"]', group_cells = '#' + this.id + ' .sudoku_board .cell[gr="' + group_position.x + '' + group_position.y + '"]', horizontal_cells_exists = $(horizontal_cells + ' span:contains(' + value + ')'), vertical_cells_exists = $(vertical_cells + ' span:contains(' + value + ')'), group_cells_exists = $(group_cells + ' span:contains(' + value + ')'), horizontal_notes = horizontal_cells + ' .note:contains(' + value + ')', vertical_notes = vertical_cells + ' .note:contains(' + value + ')', group_notes = group_cells + ' .note:contains(' + value + ')', old_value = parseInt($(this.cell).not('.notvalid').text()) || 0; if ($(this.cell).hasClass('fix')) { return } $(this.cell).find('span').text(value === 0 ? '' : value); if (this.cell !== null && (horizontal_cells_exists.length || vertical_cells_exists.length || group_cells_exists.length)) { if (old_value !== value) { $(this.cell).addClass('notvalid') } else { $(this.cell).find('span').text('') } } else { $(this.cell).removeClass('notvalid'); console.log('Value added ', value); $(horizontal_notes).remove(); $(vertical_notes).remove(); $(group_notes).remove() } this.cellsComplete = $('#' + this.id + ' .sudoku_board .cell:not(.notvalid) span:not(:empty)').length; console.log('is game over? ', this.cellsComplete, this.cellsNr, this.cellsComplete === this.cellsNr); if (this.cellsComplete === this.cellsNr) { this.gameOver() } $('#' + this.id + ' .statistics .cells_complete').text('' + this.cellsComplete + '/' + this.cellsNr); return this }; Sudoku.prototype.addNote = function (value) { console.log('addNote', value); var t = this, oldNotes = $(t.cell).find('.note'), note_width = Math.floor($(t.cell).width() / 2); if (oldNotes.length < 4) { $('<div></div>').addClass('note').css({ 'line-height': note_width + 'px', height: note_width - 1, width: note_width - 1, }).text(value).appendTo(this.cell) } return this }; Sudoku.prototype.removeNote = function (value) { if (value === 0) { $(this.cell).find('.note').remove() } else { $(this.cell).find('.note:contains(' + value + ')').remove() } return this }; Sudoku.prototype.gameOver = function () { console.log('GAME OVER!'); this.status = this.END; $('#' + this.id + ' .gameover_container').show() }; Sudoku.prototype.run = function () { this.status = this.RUNNING; var t = this; this.drawBoard(); $('#' + this.id + ' .sudoku_board .cell').on('click', function (e) { t.cellSelect(this) }); $('#' + this.id + ' .board_console .num').on('click', function (e) { var value = $.isNumeric($(this).text()) ? parseInt($(this).text()) : 0, clickMarkNotes = $(this).hasClass('note'), clickRemove = $(this).hasClass('remove'), numSelected = $(this).hasClass('selected'); if (clickMarkNotes) { console.log('clickMarkNotes'); t.markNotes = !t.markNotes; if (t.markNotes) { $(this).addClass('selected') } else { $(this).removeClass('selected'); t.removeNote(0).showConsole() } } else { if (t.markNotes) { if (!numSelected) { if (!value) { t.removeNote(0).hideConsole() } else { t.addValue(0).addNote(value).hideConsole() } } else { t.removeNote(value).hideConsole() } } else { t.removeNote(0).addValue(value).hideConsole() } } }); $('#' + this.id + ' .board_console_container').on('click', function (e) { if ($(e.target).is('.board_console_container')) { $(this).hide() } }); $(window).resize(function () { t.resizeWindow() }) }; $(function () { console.time('loading time'); $('head').append('<meta name="viewport" content="initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,width=device-width,height=device-height,target-densitydpi=device-dpi,user-scalable=yes" />'); var game = new Sudoku({ id: 'sudoku_container', fixCellsNr: 30, highlight: 1, displayTitle: 1, }); game.run(); $('#sidebar-toggle').on('click', function (e) { $('#sudoku_menu').toggleClass('open-sidebar') }); $('#' + game.id + ' .restart').on('click', function () { game.init().run() }); $('#sudoku_menu .restart').on('click', function () { game.init().run(); $('#sudoku_menu').removeClass('open-sidebar') }); console.timeEnd('loading time') });</script>
</body>

</html>